#include "VBO.h"
#include <algorithm>

namespace GeorgeLib
{

	//#define BUFFER_OFFSET(i) ((char *)NULL + (i))

	inline GLvoid* BUFFER_OFFSET(uint i)
	{
		return (char*)NULL + i;
	}

	VBO::~VBO(void)
	{
		for (uint i = 0; i < vbos.size(); ++i)
		{
			DELETE_PTR(vbos[i]);
		}
	}

	GLuint VBO::Begin(GLenum mode)
	{
		// Check if VBO::Begin() has already been called
		if (bAcceptingVertexData) return 0;
		else bAcceptingVertexData = true;

		VBO_Data *vbo = new VBO_Data;
		vbos.push_back(vbo);
		vbo->mode = mode;

		// Create a new VBO
		glGenBuffers(1, &vbo->id);

		return vbo->id;
	}

	void VBO::UpdateBegin(GLuint id)
	{
		VBO_Data *vbo = NULL;
		for (uint i = 0; i < vbos.size(); ++i)
			if (vbos[i]->id == id) { vbo = vbos[i]; break; }

		// Check if VBO::Update() has already been called
		if (bAcceptingVertexData && !vbo) return;
		else bAcceptingVertexData = true;

		vbo->vertices.clear();
		vbos.push_back(vbo);
		vbos.erase(find(vbos.begin(), vbos.end(), vbo));
	}

	void VBO::End(GLenum usage)
	{
		if (!bAcceptingVertexData) return;
		else bAcceptingVertexData = false;

		VBO_Data *vbo = vbos.back();

		// Make the vbo active
		glBindBuffer(GL_ARRAY_BUFFER, vbo->id);

		// Allocate space in video device
		glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vbo->vertices.size(), NULL, usage);

		// Upload vertex data to video device
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * vbo->vertices.size(), &vbo->vertices[0]);

		if (vbo->hasTexCoord) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
		if (vbo->hasNormal) glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(20));
		if (vbo->hasColor) glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(32));
		glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));
	}

	void VBO::UpdateEnd(GLuint id)
	{
		VBO_Data *vbo = NULL;
		for (uint i = 0; i < vbos.size(); ++i)
			if (vbos[i]->id == id) { vbo = vbos[i]; break; }

		// Check if VBO::Update() has already been called
		if (bAcceptingVertexData && !vbo) return;
		else bAcceptingVertexData = true;

		// Make the vbo active
		glBindBuffer(GL_ARRAY_BUFFER, vbo->id);

		// Upload vertex data to video device
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * vbo->vertices.size(), &vbo->vertices[0]);

		if (vbo->hasTexCoord) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
		if (vbo->hasNormal) glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(20));
		if (vbo->hasColor) glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(32));
		glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));
	}

	void VBO::Draw(void)
	{
		for (uint i = 0; i < vbos.size(); ++i)
		{
			VBO_Data *vbo = vbos[i];
			// Make the VBO active
			glBindBuffer(GL_ARRAY_BUFFER, vbo->id);

			// Tell OpenGL what we're gonna draw
			if (vbo->hasTexCoord) glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			if (vbo->hasNormal) glEnableClientState(GL_NORMAL_ARRAY);
			if (vbo->hasColor) glEnableClientState(GL_COLOR_ARRAY);
			glEnableClientState(GL_VERTEX_ARRAY);

			if (vbo->hasTexCoord) glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
			if (vbo->hasNormal) glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(20));
			if (vbo->hasColor) glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(32));
			glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));

			// Actually draw the vertices, giving the number of vertices provided
			glDrawArrays(vbo->mode, 0, vbo->vertices.size());

			if (vbo->hasTexCoord) glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			if (vbo->hasNormal) glDisableClientState(GL_NORMAL_ARRAY);
			if (vbo->hasColor) glDisableClientState(GL_COLOR_ARRAY);
			glDisableClientState(GL_VERTEX_ARRAY);
		}
	}

	void VBO::AddVertex(float x, float y, float z)
	{
		if (!bAcceptingVertexData) return;
		else
		{
			Vertex *v = new Vertex;
			v->pos[0] = x;
			v->pos[1] = y;
			v->pos[2] = z;
			vbos.back()->vertices.push_back(*v);
		}
	}

	void VBO::AddVertex(vec3 v)
	{
		if (!bAcceptingVertexData) return;
		else this->AddVertex(v.x, v.y, v.z);
	}

	void VBO::AddVertex(float *v)
	{
		if (!bAcceptingVertexData) return;
		else this->AddVertex(v[0], v[1], v[2]);
	}

	void VBO::AddNormal(float x, float y, float z)
	{
		if (!bAcceptingVertexData) return;
		else
		{
			Vertex *v = &vbos.back()->vertices.back();
			v->normal[0] = x;
			v->normal[1] = y;
			v->normal[2] = z;
			vbos.back()->hasNormal = true;
		}
	}

	void VBO::AddNormal(vec3 v)
	{
		if (!bAcceptingVertexData) return;
		else AddNormal(v.x, v.y, v.z);
	}

	void VBO::AddNormal(float *v)
	{
		if (!bAcceptingVertexData) return;
		else this->AddNormal(v[0], v[1], v[2]);
	}

	void VBO::AddTexCoord(float u, float v)
	{
		if (!bAcceptingVertexData) return;
		else
		{
			Vertex *vert = &vbos.back()->vertices.back();
			vert->texcoord[0] = u;
			vert->texcoord[1] = v;
			vbos.back()->hasTexCoord = true;
		}
	}

	void VBO::AddTexCoord(vec2 v)
	{
		if (!bAcceptingVertexData) return;
		else AddTexCoord(v.x, v.y);
	}

	void VBO::AddTexCoord(float *v)
	{
		if (!bAcceptingVertexData) return;
		else this->AddTexCoord(v[0], v[1]);
	}

	void VBO::AddColor(float r, float g, float b, float a)
	{
		if (!bAcceptingVertexData) return;
		else
		{
			Vertex *v = &vbos.back()->vertices.back();
			v->color[0] = r;
			v->color[1] = g;
			v->color[2] = b;
			v->color[3] = a;
			vbos.back()->hasColor = true;
		}
	}

	void VBO::AddColor(vec3 v, float a)
	{
		if (!bAcceptingVertexData) return;
		else AddColor(v.x, v.y, v.z, a);
	}

	void VBO::AddColor(float *v)
	{
		if (!bAcceptingVertexData) return;
		else this->AddColor(v[0], v[1], v[2], (*(v + 3)) ? v[3] : 1.0f);
	}

	void VBO::SetColor(GLuint id, float r, float g, float b, float a)
	{
		VBO_Data *vbo = NULL;
		for (uint i = 0; i < vbos.size(); ++i)
			if (vbos[i]->id == id) { vbo = vbos[i]; break; }

		if (!vbo) return;

		for (uint i = 0; i < vbo->vertices.size(); ++i)
		{
			Vertex *v = &vbo->vertices[i];
			v->color[0] = r;
			v->color[1] = g;
			v->color[2] = b;
			v->color[3] = a;
		}

		vbo->hasColor = true;
	}

} // namespace GeorgeLib